package cn.com.bluemoon.daps.sync.core.sync.executor;

import cn.com.bluemoon.daps.common.enums.SyncStatus;
import cn.com.bluemoon.daps.common.enums.SyncToEnv;
import cn.com.bluemoon.daps.common.toolkit.BmAssetUtils;
import cn.com.bluemoon.daps.sync.base.BmScript;
import cn.com.bluemoon.daps.sync.base.CompareResult;
import cn.com.bluemoon.daps.sync.base.ModifyType;
import cn.com.bluemoon.daps.sync.base.SqlCompareResult;
import cn.com.bluemoon.daps.sync.core.sync.GlobalConfig;
import cn.com.bluemoon.daps.sync.core.sync.compare.TableCompareRes;
import cn.com.bluemoon.daps.sync.core.sync.log.EvenType;
import cn.com.bluemoon.daps.sync.core.sync.log.SyncEnvEventFactory;
import cn.com.bluemoon.daps.sync.core.sync.log.SyncEnvLog;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * 脚本执行器基类
 *
 * @author Jarod.Kong
 * @date 2021-06-05 11:17
 **/
@Slf4j
public abstract class SqlExecutor {
    /**
     * 日志收集、执行类型、入参对比脚本数据、全局变量、是否异步
     */
    private final Map<String, SyncEnvLog> keyAndSyncEnvLog = new HashMap<>();
    protected ExecutorType executorType;
    protected GlobalConfig globalConfig;
    protected boolean async;
    /**
     * 把执行器的初始化数据入库持久化？ true入库，false只保存在内存中
     */
    protected boolean compareResult2DB;
    /**
     * 是否吧对比结果初始化写入到DB
     */
    private List<TableCompareRes<SqlCompareResult>> tableInfoSqlResult = new ArrayList<>();

    protected SqlExecutor(ExecutorType executorType, GlobalConfig globalConfig) {
        this.executorType = executorType;
        this.globalConfig = globalConfig;
        BmAssetUtils.notNull(globalConfig, "全局配置不可为空");
    }

    /**
     * log format tool
     *
     * @param msg   内容
     * @param count 加'-'重复数量
     * @return str
     */
    static String appendMsg(String msg, int count) {
        assert msg != null;
        count = count <= 0 ? 50 : count;
        return StrUtil.repeat("-", count) + msg + StrUtil.repeat("-", count);
    }

    /**
     * 获取异常详细信息，知道出了什么错，错在哪个类的第几行 .
     *
     * @param ex 异常
     * @return str
     */
    static String getThrowableDetail(Throwable ex) {
        try (StringWriter sw = new StringWriter();
             PrintWriter pw = new PrintWriter(sw, true)) {
            ex.printStackTrace(pw);
            pw.flush();
            sw.flush();
            return sw.toString();
        } catch (IOException e) {
            log.error("生成异常代码异常", e);
            return e.getMessage();
        }
    }

    /**
     * 执行脚本前初始化对比数据
     */
    protected SqlExecutor initSyncPreCompareResultBySyncToEnv() {
        return initSyncPreCompareResultBySyncToEnv(getTableInfoSqlResult());
    }

    /**
     * 执行脚本前初始化对比数据
     *
     * @param tableCompareRes res
     */
    protected SqlExecutor initSyncPreCompareResultBySyncToEnv(List<TableCompareRes<SqlCompareResult>> tableCompareRes) {
        BmAssetUtils.isTrue(!tableCompareRes.isEmpty(), "执行前先指定对比结果数据");
        SyncToEnv syncToEnv = this.globalConfig.getSyncToEnv();
        log.info("同步到{}，执行脚本前{}，需求", syncToEnv, syncToEnv.isSyncPreCompareResult2DB() ? "入库持久化" : "不入库存储内存中");
        if (syncToEnv.isSyncPreCompareResult2DB()) {
            addCompareResult2DB(tableCompareRes);
        } else {
            addCompareResult2Memory(tableCompareRes);
        }
        return this;
    }

    /**
     * 增排序，按getModifyType#getRank asc排序
     *
     * @return 结果
     */
    public List<TableCompareRes<SqlCompareResult>> getTableInfoSqlResult() {
        for (TableCompareRes<SqlCompareResult> res : tableInfoSqlResult) {
            List<CompareResult<SqlCompareResult>> sortCompares = res.getCompareResults().stream().sorted((o1, o2) -> {
                if (o1 == null || o1.getModifyType() == null) return -1;
                if (o2 == null || o2.getModifyType() == null) return 1;
                return o1.getModifyType().getRank().compareTo(o2.getModifyType().getRank());
            }).collect(Collectors.toList());
            res.setCompareResults(sortCompares);
        }
        return tableInfoSqlResult;
    }

    public List<TableCompareRes<SqlCompareResult>> getCompareResultTableLevelSql() {
        return getTableInfoSqlResult().stream().filter(t -> {
            return ModifyType.ModifyLevel.TABLE.equals(t.getTableInfo().getModifyLevel());
        }).collect(Collectors.toList());
    }

    public List<TableCompareRes<SqlCompareResult>> getCompareResultNoTableLevelSql() {
        return getTableInfoSqlResult().stream().filter(t -> {
            return !ModifyType.ModifyLevel.TABLE.equals(t.getTableInfo().getModifyLevel());
        }).collect(Collectors.toList());
    }

    protected SyncEnvLog appendLog(CompareResult<SqlCompareResult> c) {
        String tableLogKey = SyncEnvLog.generateKey(c.getModelId(), c.getTableId());
        SyncEnvLog envLog = keyAndSyncEnvLog.get(tableLogKey);
        return envLog;
    }

    /**
     * 收集对比的脚本结果集
     *
     * @param compareResult 对比结果集
     * @return 执行器
     */
    public SqlExecutor sqlResult(List<Map<ModifyType.ModifyLevel, List<CompareResult<SqlCompareResult>>>> compareResult) {
        List<TableCompareRes<SqlCompareResult>> tableInfoSqlResult = getTableInfoSqlResult(compareResult);
        this.tableInfoSqlResult = tableInfoSqlResult;
        return this;
    }

    public SqlExecutor sqlResult0(List<TableCompareRes<SqlCompareResult>> tableInfoSqlResult) {
        this.tableInfoSqlResult = tableInfoSqlResult;
        return this;
    }

    /**
     * @return 每个表的执行结果
     */
    private List<TableCompareRes<SqlCompareResult>> getTableInfoSqlResult(List<Map<ModifyType.ModifyLevel, List<CompareResult<SqlCompareResult>>>> res) {
        List<TableCompareRes<SqlCompareResult>> tableCompareRes = res.stream().map(m -> {
            List<CompareResult<SqlCompareResult>> collect = m.entrySet().stream().flatMap(d -> d.getValue().stream())
                    .collect(Collectors.toList());
            return collect;
        }).map(c -> {
            CompareResult<SqlCompareResult> result = c.stream().filter(t -> t.getTableId() != null && t.getTableName() != null).findFirst().orElse(c.get(0));
            return TableCompareRes.<SqlCompareResult>builder().compareResults(c).uniqueKey(IdWorker.getIdStr()).tableInfo(result).globalConfig(globalConfig).build();
        }).collect(Collectors.toList());
        return tableCompareRes;
    }

    /**
     * 收集对比的脚本结果集
     *
     * @param compareResult 对比结果集
     * @return 执行器
     */
    public SqlExecutor sqlResult(Map<ModifyType.ModifyLevel, List<CompareResult<SqlCompareResult>>> compareResult) {
        return sqlResult(Collections.singletonList(compareResult));
    }

    /**
     * 是否异步执行
     *
     * @return 执行器
     */
    public SqlExecutor async() {
        this.async = true;
        return this;
    }

    /**
     * 对外触发jdbc脚本执行器方法
     *
     * @return 执行日志
     */
    public boolean execute() {
        Boolean res = false;
        if (async) {
            CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(this::execute0);
            try {
                res = future.get();
            } catch (Exception e) {
                log.error("异步执行错误！！", e);
                res = Boolean.FALSE;
            }
        } else {
            res = execute0();
        }
        return res;
    }

    protected SqlExecutor addCompareResult2DB() {
        this.compareResult2DB = true;
        return addCompareResultInit(getTableInfoSqlResult());
    }


    protected SqlExecutor addCompareResult2DB(List<TableCompareRes<SqlCompareResult>> tableInfoSqlResult) {
        this.compareResult2DB = true;
        return addCompareResultInit(tableInfoSqlResult);
    }

    protected SqlExecutor addCompareResult2Memory(List<TableCompareRes<SqlCompareResult>> tableInfoSqlResult) {
        this.compareResult2DB = false;
        return addCompareResultInit(tableInfoSqlResult);
    }

    protected SqlExecutor addCompareResult2Memory() {
        this.compareResult2DB = false;
        return addCompareResultInit(getTableInfoSqlResult());
    }

    private SqlExecutor addCompareResultInit(List<TableCompareRes<SqlCompareResult>> tableInfoSqlResult) {
        if (tableInfoSqlResult == null) {
            return this;
        }
        List<SyncEnvLog> envLogs = new ArrayList<>(tableInfoSqlResult.size());
        // 转为一个map的结果为一个行数据
        for (TableCompareRes<SqlCompareResult> res : tableInfoSqlResult) {
            SyncEnvLog envLog = createSyncEnvLog(res);
            log.info("推送执行日志记录消息，{}", envLogs);
            envLogs.add(envLog);
            if (compareResult2DB) {
                SyncEnvEventFactory.publishEvent(globalConfig, envLog, EvenType.SAVE_OR_UPDATE);
            }
        }
        Map<String, SyncEnvLog> keyAndSyncEnvLog = envLogs.stream().collect(Collectors.toMap(SyncEnvLog::generateKey, d -> d, (a, b) -> b));
        this.setKeyAndSyncEnvLog(keyAndSyncEnvLog);
        return this;
    }

    /**
     * 根据表结果信息转成SyncEnvLog用于入库数据用使用
     * @param res r
     * @return log
     */
    protected SyncEnvLog createSyncEnvLog(TableCompareRes<SqlCompareResult> res) {
        SyncEnvLog envLog = SyncEnvEventFactory.createLog(globalConfig, globalConfig.getSyncToEnv());
        envLog.setKey(res.getUniqueKey());
        envLog.setTableId(res.getTableInfo().getTableId());
        envLog.setTableName(res.getTableInfo().getTableName());
        envLog.setSyncStartTime(LocalDateTime.now());
        List<BmScript> bmScripts = res.getCompareResults().stream().map(c -> c.getData().getScript()).collect(Collectors.toList());
        envLog.setSyncDdl(bmScripts.stream().map(BmScript::getSql).collect(Collectors.toList()));
        envLog.setSyncPtsql(bmScripts.stream().map(BmScript::getPtSql).collect(Collectors.toList()));
        envLog.setSyncStatus(SyncStatus.SYNC_DOING);
        envLog.genSyncLogPath();
        envLog.setTableCreateSql(res.getTableInfo().getTableCreateSql());
        return envLog;
    }

    /**
     * 执行器内部主要方法
     *
     * @return b
     */
    protected abstract boolean execute0();

    public Map<String, SyncEnvLog> getKeyAndSyncEnvLog() {
        return keyAndSyncEnvLog;
    }

    protected void setKeyAndSyncEnvLog(Map<String, SyncEnvLog> keyAndSyncEnvLog) {
        for (Map.Entry<String, SyncEnvLog> entry : keyAndSyncEnvLog.entrySet()) {
            if (!this.keyAndSyncEnvLog.containsKey(entry.getKey())) {
                this.keyAndSyncEnvLog.put(entry.getKey(), entry.getValue());
            }
        }
    }

    public enum ExecutorType {
        JDBC, PT
    }
}